@@ -1,4 +1,5 @@ |
||
1 | 1 |
language: ruby |
2 |
+cache: bundler |
|
2 | 3 |
bundler_args: --without development production |
3 | 4 |
env: |
4 | 5 |
- APP_SECRET_TOKEN=b2724973fd81c2f4ac0f92ac48eb3f0152c4a11824c122bcf783419a4c51d8b9bba81c8ba6a66c7de599677c7f486242cf819775c433908e77c739c5c8ae118d TWITTER_OAUTH_KEY=twitteroauthkey TWITTER_OAUTH_SECRET=twitteroauthsecret |
@@ -0,0 +1,76 @@ |
||
1 |
+module Agents |
|
2 |
+ class ChangeDetectorAgent < Agent |
|
3 |
+ cannot_be_scheduled! |
|
4 |
+ |
|
5 |
+ description <<-MD |
|
6 |
+ The ChangeDetectorAgent receives a stream of events and emits a new event when a property of the received event changes. |
|
7 |
+ |
|
8 |
+ `property` specifies the property to be watched. |
|
9 |
+ |
|
10 |
+ `expected_update_period_in_days` is used to determine if the Agent is working. |
|
11 |
+ |
|
12 |
+ The resulting event will be a copy of the received event. |
|
13 |
+ MD |
|
14 |
+ |
|
15 |
+ event_description <<-MD |
|
16 |
+ This will change based on the source event. If you were event from the ShellCommandAgent, your outbound event might look like: |
|
17 |
+ |
|
18 |
+ { |
|
19 |
+ 'command' => 'pwd', |
|
20 |
+ 'path' => '/home/Huginn', |
|
21 |
+ 'exit_status' => '0', |
|
22 |
+ 'errors' => '', |
|
23 |
+ 'output' => '/home/Huginn' |
|
24 |
+ } |
|
25 |
+ MD |
|
26 |
+ |
|
27 |
+ def default_options |
|
28 |
+ { |
|
29 |
+ 'property' => '{{output}}', |
|
30 |
+ 'expected_update_period_in_days' => 1 |
|
31 |
+ } |
|
32 |
+ end |
|
33 |
+ |
|
34 |
+ def validate_options |
|
35 |
+ unless options['property'].present? && options['expected_update_period_in_days'].present? |
|
36 |
+ errors.add(:base, "The property and expected_update_period_in_days fields are all required.") |
|
37 |
+ end |
|
38 |
+ end |
|
39 |
+ |
|
40 |
+ def working? |
|
41 |
+ event_created_within?(interpolated['expected_update_period_in_days']) && !recent_error_logs? |
|
42 |
+ end |
|
43 |
+ |
|
44 |
+ def receive(incoming_events) |
|
45 |
+ incoming_events.each do |event| |
|
46 |
+ handle(interpolated(event), event) |
|
47 |
+ end |
|
48 |
+ end |
|
49 |
+ |
|
50 |
+ private |
|
51 |
+ |
|
52 |
+ def handle(opts, event = nil) |
|
53 |
+ property = opts['property'] |
|
54 |
+ if has_changed?(property) |
|
55 |
+ created_event = create_event :payload => event.payload |
|
56 |
+ |
|
57 |
+ log("Propagating new event as property has changed to #{property} from #{last_property}", :outbound_event => created_event, :inbound_event => event ) |
|
58 |
+ update_memory(property) |
|
59 |
+ else |
|
60 |
+ log("Not propagating as incoming event has not changed from #{last_property}.", :inbound_event => event ) |
|
61 |
+ end |
|
62 |
+ end |
|
63 |
+ |
|
64 |
+ def has_changed?(property) |
|
65 |
+ property != last_property |
|
66 |
+ end |
|
67 |
+ |
|
68 |
+ def last_property |
|
69 |
+ self.memory['last_property'] |
|
70 |
+ end |
|
71 |
+ |
|
72 |
+ def update_memory(property) |
|
73 |
+ self.memory['last_property'] = property |
|
74 |
+ end |
|
75 |
+ end |
|
76 |
+end |
@@ -1,27 +1,28 @@ |
||
1 |
-<div class='container'> |
|
2 |
- <div class='row'> |
|
3 |
- <div class='span8 offset2'> |
|
4 |
- <div class='well'> |
|
5 |
- <h2>Forgot your password?</h2> |
|
1 |
+<div class='row'> |
|
2 |
+ <div class='col-md-6 col-md-offset-3'> |
|
3 |
+ <div class='well'> |
|
4 |
+ <h2>Forgot your password?</h2> |
|
6 | 5 |
|
7 |
- <%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post, :class => 'form-horizontal' }) do |f| %> |
|
8 |
- <%= devise_error_messages! %> |
|
6 |
+ <%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post, :class => 'form-horizontal' }) do |f| %> |
|
7 |
+ <%= devise_error_messages! %> |
|
9 | 8 |
|
10 |
- <div class="control-group"> |
|
11 |
- <%= f.label :login, :class => 'control-label' %> |
|
12 |
- <div class="controls"> |
|
13 |
- <%= f.text_field :login, :class => 'span4' %> |
|
14 |
- </div> |
|
9 |
+ <div class="form-group"> |
|
10 |
+ <%= f.label :login, :class => 'col-md-2 col-md-offset-2 control-label' %> |
|
11 |
+ <div class="col-md-6"> |
|
12 |
+ <%= f.text_field :login, :class => 'form-control' %> |
|
15 | 13 |
</div> |
14 |
+ </div> |
|
16 | 15 |
|
17 |
- <div class='form-actions'> |
|
16 |
+ <div class="form-group"> |
|
17 |
+ <div class="col-md-offset-4 col-md-10"> |
|
18 | 18 |
<%= f.submit "Send me reset password instructions", :class => "btn btn-primary" %> |
19 | 19 |
</div> |
20 |
- <% end %> |
|
20 |
+ </div> |
|
21 |
+ <% end %> |
|
21 | 22 |
|
22 |
- <%= render "devise/shared/links" %> |
|
23 |
+ <hr> |
|
23 | 24 |
|
24 |
- </div> |
|
25 |
+ <%= render "devise/shared/links" %> |
|
25 | 26 |
</div> |
26 | 27 |
</div> |
27 | 28 |
</div> |
@@ -1,14 +1,28 @@ |
||
1 |
-<h2>Resend unlock instructions</h2> |
|
1 |
+<div class='row'> |
|
2 |
+ <div class='col-md-6 col-md-offset-3'> |
|
3 |
+ <div class='well'> |
|
4 |
+ <h2>Resend unlock instructions</h2> |
|
2 | 5 |
|
3 |
-<%= form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post }) do |f| %> |
|
4 |
- <%= devise_error_messages! %> |
|
6 |
+ <%= form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post, :class => 'form-horizontal' }) do |f| %> |
|
7 |
+ <%= devise_error_messages! %> |
|
5 | 8 |
|
6 |
- <div> |
|
7 |
- <%= f.label :email %> |
|
8 |
- <%= f.email_field :email %> |
|
9 |
- </div> |
|
9 |
+ <div class="form-group"> |
|
10 |
+ <%= f.label :email, :class => 'col-md-2 col-md-offset-2 control-label' %> |
|
11 |
+ <div class="col-md-6"> |
|
12 |
+ <%= f.text_field :email, :class => 'form-control' %> |
|
13 |
+ </div> |
|
14 |
+ </div> |
|
15 |
+ |
|
16 |
+ <div class="form-group"> |
|
17 |
+ <div class="col-md-offset-4 col-md-10"> |
|
18 |
+ <%= f.submit "Resend unlock instructions", :class => "btn btn-primary" %> |
|
19 |
+ </div> |
|
20 |
+ </div> |
|
21 |
+ <% end %> |
|
10 | 22 |
|
11 |
- <div><%= f.submit "Resend unlock instructions" %></div> |
|
12 |
-<% end %> |
|
23 |
+ <hr> |
|
13 | 24 |
|
14 |
-<%= render "devise/shared/links" %> |
|
25 |
+ <%= render "devise/shared/links" %> |
|
26 |
+ </div> |
|
27 |
+ </div> |
|
28 |
+</div> |
@@ -0,0 +1,98 @@ |
||
1 |
+require 'spec_helper' |
|
2 |
+ |
|
3 |
+describe Agents::ChangeDetectorAgent do |
|
4 |
+ def create_event(output=nil) |
|
5 |
+ event = Event.new |
|
6 |
+ event.agent = agents(:jane_weather_agent) |
|
7 |
+ event.payload = { |
|
8 |
+ :command => 'some-command', |
|
9 |
+ :output => output |
|
10 |
+ } |
|
11 |
+ event.save! |
|
12 |
+ |
|
13 |
+ event |
|
14 |
+ end |
|
15 |
+ |
|
16 |
+ before do |
|
17 |
+ @valid_params = { |
|
18 |
+ :property => "{{output}}", |
|
19 |
+ :expected_update_period_in_days => "1", |
|
20 |
+ } |
|
21 |
+ |
|
22 |
+ @checker = Agents::ChangeDetectorAgent.new(:name => "somename", :options => @valid_params) |
|
23 |
+ @checker.user = users(:jane) |
|
24 |
+ @checker.save! |
|
25 |
+ end |
|
26 |
+ |
|
27 |
+ describe "validation" do |
|
28 |
+ before do |
|
29 |
+ @checker.should be_valid |
|
30 |
+ end |
|
31 |
+ |
|
32 |
+ it "should validate presence of property" do |
|
33 |
+ @checker.options[:property] = nil |
|
34 |
+ @checker.should_not be_valid |
|
35 |
+ end |
|
36 |
+ |
|
37 |
+ it "should validate presence of property" do |
|
38 |
+ @checker.options[:expected_update_period_in_days] = nil |
|
39 |
+ @checker.should_not be_valid |
|
40 |
+ end |
|
41 |
+ end |
|
42 |
+ |
|
43 |
+ describe "#working?" do |
|
44 |
+ before :each do |
|
45 |
+ # Need to create an event otherwise event_created_within? returns nil |
|
46 |
+ event = create_event |
|
47 |
+ @checker.receive([event]) |
|
48 |
+ end |
|
49 |
+ |
|
50 |
+ it "is when event created within :expected_update_period_in_days" do |
|
51 |
+ @checker.options[:expected_update_period_in_days] = 2 |
|
52 |
+ @checker.should be_working |
|
53 |
+ end |
|
54 |
+ |
|
55 |
+ it "isnt when event created outside :expected_update_period_in_days" do |
|
56 |
+ @checker.options[:expected_update_period_in_days] = 2 |
|
57 |
+ |
|
58 |
+ time_travel_to 2.days.from_now do |
|
59 |
+ @checker.should_not be_working |
|
60 |
+ end |
|
61 |
+ end |
|
62 |
+ end |
|
63 |
+ |
|
64 |
+ describe "#receive" do |
|
65 |
+ before :each do |
|
66 |
+ @event = create_event("2014-07-01") |
|
67 |
+ end |
|
68 |
+ |
|
69 |
+ it "creates events when memory is empty" do |
|
70 |
+ @event.payload[:output] = "2014-07-01" |
|
71 |
+ expect { |
|
72 |
+ @checker.receive([@event]) |
|
73 |
+ }.to change(Event, :count).by(1) |
|
74 |
+ Event.last.payload[:command].should == @event.payload[:command] |
|
75 |
+ Event.last.payload[:output].should == @event.payload[:output] |
|
76 |
+ end |
|
77 |
+ |
|
78 |
+ it "creates events when new event changed" do |
|
79 |
+ @event.payload[:output] = "2014-07-01" |
|
80 |
+ @checker.receive([@event]) |
|
81 |
+ |
|
82 |
+ event = create_event("2014-08-01") |
|
83 |
+ |
|
84 |
+ expect { |
|
85 |
+ @checker.receive([event]) |
|
86 |
+ }.to change(Event, :count).by(1) |
|
87 |
+ end |
|
88 |
+ |
|
89 |
+ it "does not create event when no change" do |
|
90 |
+ @event.payload[:output] = "2014-07-01" |
|
91 |
+ @checker.receive([@event]) |
|
92 |
+ |
|
93 |
+ expect { |
|
94 |
+ @checker.receive([@event]) |
|
95 |
+ }.to change(Event, :count).by(0) |
|
96 |
+ end |
|
97 |
+ end |
|
98 |
+end |
@@ -85,7 +85,7 @@ describe EventDrop do |
||
85 | 85 |
before do |
86 | 86 |
@event = Event.new |
87 | 87 |
@event.agent = agents(:jane_weather_agent) |
88 |
- @event.created_at = Time.at(1400000000) |
|
88 |
+ @event.created_at = Time.now |
|
89 | 89 |
@event.payload = { |
90 | 90 |
'title' => 'some title', |
91 | 91 |
'url' => 'http://some.site.example.org/', |
@@ -115,6 +115,6 @@ describe EventDrop do |
||
115 | 115 |
|
116 | 116 |
it 'should have created_at' do |
117 | 117 |
t = '{{created_at | date:"%FT%T%z" }}' |
118 |
- interpolate(t, @event).should eq('2014-05-13T09:53:20-0700') |
|
118 |
+ interpolate(t, @event).should eq(@event.created_at.strftime("%FT%T%z")) |
|
119 | 119 |
end |
120 | 120 |
end |
@@ -9,7 +9,9 @@ shared_examples_for LiquidInterpolatable do |
||
9 | 9 |
"escape" => "This should be {{hello_world | uri_escape}}" |
10 | 10 |
} |
11 | 11 |
|
12 |
- @checker = described_class.new(:name => "somename", :options => @valid_params) |
|
12 |
+ @checker = new_instance |
|
13 |
+ @checker.name = "somename" |
|
14 |
+ @checker.options = @valid_params |
|
13 | 15 |
@checker.user = users(:jane) |
14 | 16 |
|
15 | 17 |
@event = Event.new |